﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
using System.Net;

namespace Minecraft.Data.NBT {
    public class NBTWriter {
        private static UTF8Encoding enc = new UTF8Encoding();

        public static void SaveNBT(String filename, Tag tag) {

            using (MemoryStream mem = new MemoryStream()) {
                Byte[] tagBuffer = EncodeTag(tag, TagType.Unknown, true);
                mem.Write(tagBuffer, 0, tagBuffer.Length);
                mem.Seek(0, SeekOrigin.Begin);

                using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read)) {
                    using (GZipStream gzip = new GZipStream(fs, CompressionMode.Compress)) {
                        Byte[] buffer = new byte[1024];
                        Int32 read = 0;
                        while ((read = mem.Read(buffer, 0, buffer.Length)) > 0)
                            gzip.Write(buffer, 0, read);

                        gzip.Close();
                    }
                    fs.Close();
                }
                mem.Close();
            }
        }

        private static byte[] EncodeTag(Tag tag, TagType knownType, Boolean named) {
            List<Byte> buffer = new List<byte>();
            TagType encodeType = tag.Type;

            if (knownType == TagType.Unknown)
                buffer.Add(Convert.ToByte(encodeType));
            else
                encodeType = knownType;

            if (named) {
                if (!String.IsNullOrEmpty(tag.Name)) {
                    Byte[] nameBytes = enc.GetBytes(tag.Name);
                    Int16 nameLength = IPAddress.HostToNetworkOrder(Convert.ToInt16(nameBytes.Length));
                    buffer.AddRange(BitConverter.GetBytes(nameLength));
                    buffer.AddRange(nameBytes);
                }
                else {
                    Int16 nameLength = IPAddress.HostToNetworkOrder(Convert.ToInt16(0));
                    buffer.AddRange(BitConverter.GetBytes(nameLength));
                }
            }

            switch (encodeType) {
                case TagType.Byte:
                    buffer.Add((Byte)tag.Payload);
                    break;

                case TagType.Short:
                    buffer.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(tag.Payload))));
                    break;

                case TagType.Int:
                    buffer.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt32(tag.Payload))));
                    break;

                case TagType.Long:
                    buffer.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt64(tag.Payload))));
                    break;

                case TagType.Float:
                    Byte[] fa = BitConverter.GetBytes(Convert.ToSingle(tag.Payload));
                    Array.Reverse(fa);
                    buffer.AddRange(fa);
                    break;

                case TagType.Double:
                    Byte[] da = BitConverter.GetBytes(Convert.ToDouble(tag.Payload));
                    Array.Reverse(da);
                    buffer.AddRange(da);
                    break;

                case TagType.ByteArray:
                    buffer.AddRange((Byte[])tag.Payload);
                    break;

                case TagType.String:
                    Byte[] nameBytes = enc.GetBytes(tag.Name);
                    Int16 nameLength = IPAddress.HostToNetworkOrder(Convert.ToInt16(nameBytes.Length));
                    buffer.AddRange(BitConverter.GetBytes(nameLength));
                    buffer.AddRange(nameBytes);
                    break;

                case TagType.List:
                    buffer.Add(Convert.ToByte(tag.ListType));
                    buffer.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt32(tag.Children.Count))));
                    foreach (var item in tag.Children)
                        buffer.AddRange(EncodeTag(item, tag.ListType, false));
                    break;

                case TagType.Compound:
                    foreach (var item in tag.Children)
                        buffer.AddRange(EncodeTag(item, TagType.Unknown, true));
                    buffer.Add(Convert.ToByte(TagType.End));
                    break;

                default:
                    break;
            }

            return buffer.ToArray();
        }
    }
}
